package gogen

import (
	_ "embed"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)

// 模式
//  1. 存在不处理
//  3. 只更新路由，添加方法
const (
	NoProcessing = "0"
	Processing   = "1"
)

const defaultLogicPackage = "logic"

//go:embed handler.tpl
var handlerTemplate string

func genHandlers(dir, rootPkg string, api *spec.ApiSpec) error {

	for _, apiSpec := range api.Srvs {
		module := api.Module
		if apiSpec.Module != "" {
			module = api.Module + "/" + apiSpec.Module
		}

		if err := genHandler(dir, module, rootPkg, api.Info, apiSpec); err != nil {
			return err
		}
	}

	return nil
}

func genHandler(dir, module, rootPkg string, info spec.Info, api *spec.ApiSpec) error {

	filename := filepath.Join(dir, fmt.Sprintf("%s/%s/%s.go", handlerDir, module, strings.ToLower(api.Service.Name)))

	exists, err := PathExists(filename)
	if err != nil {
		return err
	}
	if !exists {
		return genFile(genHandlerNotExits(dir, module, rootPkg, api))
	}

	mod, ok := info.Properties["serviceMod"]
	if !ok || mod != Processing {
		// 不处理
		return nil
	}
	// 处理
	cfg, err := genHandlerIsExits(filename, dir, module, api)
	if err != nil {
		return err
	}
	return genFile(cfg)
}

func genHandlerIsExits(filename, dir, module string, api *spec.ApiSpec) (fileGenConfig, error) {

	// 更新handle中的logic field
	existsLogics := make(map[string]struct{})
	content, err := addContentToAFile(filename, fmt.Sprintf("type %s struct", api.Service.Name), func(s string) {
		m := strings.TrimPrefix(s, "\t")
		idx := strings.Index(m, " ")
		if idx == -1 {
			return
		}
		existsLogics[m[:idx]] = struct{}{}
	}, func(positionStartIdx, positionEndIdx int, contents []string) []string {
		logicList, _, _ := genHandlerLogics(api.Service.Logics, existsLogics)
		if len(logicList) == 0 {
			return contents
		}
		contents = append(contents[:positionEndIdx], append([]string{logicList}, contents[positionEndIdx:]...)...)
		return contents
	})
	if err != nil {
		return fileGenConfig{}, err
	}

	// 更新handle中的routeList
	existsMethods := make(map[string]struct{})
	content, err = addContentToAFile(content, "InitRegister(engine *gin.Engine)", func(s string) {
		if strings.Contains(s, "engine.Group") {
			return
		}

		sidx := strings.Index(s, ", h.")
		if sidx < 0 {
			return
		}

		eidx := strings.LastIndex(s, ")")
		if strings.Contains(s, ".svcCtx") {
			eidx = strings.Index(s, ", h.svcCtx.")
		}

		existsMethods[s[sidx+4:eidx]] = struct{}{}

	}, func(positionStartIdx, positionEndIdx int, contents []string) []string {
		handleRouteList, methods, _ := genHandlerRouteList(dir, strings.ToUpper(api.Service.Name[:1])+api.Service.Name[1:], api.Service.Groups, existsMethods)
		if len(handleRouteList) == 0 {
			return contents
		}

		contents = append(contents[:positionStartIdx+1], append([]string{handleRouteList}, contents[positionEndIdx:]...)...)
		contents = append(contents, methods)

		return contents
	})
	if err != nil {
		return fileGenConfig{}, err
	}

	os.Remove(filename)

	return fileGenConfig{
		dir:             dir,
		subdir:          fmt.Sprintf("%s/%s", handlerDir, module),
		filename:        fmt.Sprintf("%s.go", strings.ToLower(api.Service.Name)),
		templateName:    "isexitsTemplate",
		category:        category,
		templateFile:    isexitsTemplateFile,
		builtinTemplate: isexitsTemplate,
		data: map[string]any{
			"content": content,
		},
	}, nil
}

func genHandlerNotExits(dir, module, rootPkg string, api *spec.ApiSpec) fileGenConfig {
	logicList, newLogicsParam, newLogicsFields := genHandlerLogics(api.Service.Logics, nil)

	handleRouteList, methods, isDomain := genHandlerRouteList(dir, strings.ToUpper(api.Service.Name[:1])+api.Service.Name[1:], api.Service.Groups, nil)

	pkgName := module
	if strs := strings.Split(module, "/"); len(strs) > 1 {
		pkgName = strs[len(strs)-1]
	}

	return fileGenConfig{
		dir:             dir,
		subdir:          fmt.Sprintf("%s/%s", handlerDir, module),
		filename:        fmt.Sprintf("%s.go", strings.ToLower(api.Service.Name)),
		templateName:    "handlerTemplate",
		category:        category,
		templateFile:    handlerTemplateFile,
		builtinTemplate: handlerTemplate,
		data: map[string]any{
			"pkgName":         pkgName,
			"import":          genHandlerImport(isDomain, logicList, rootPkg, api),
			"handle":          strings.ToUpper(api.Service.Name[:1]) + api.Service.Name[1:],
			"logics":          logicList,
			"newLogicsParam":  newLogicsParam,
			"newLogicsFields": newLogicsFields,
			"handleRouteList": handleRouteList,
			"methods":         methods,
		},
	}
}

func genHandlerLogics(logics []string, filter map[string]struct{}) (logicList string, newLogicsParam string, newLogicsFields string) {
	if len(logics) == 0 {
		return
	}

	newLogicsParam += ", "

	for i, logic := range logics {
		flag := fmt.Sprintf("%s", strings.ToLower(logic[:1])+logic[1:])

		if filter != nil && len(filter) > 0 {
			if _, ok := filter[flag]; ok {
				continue
			}
		}

		logicList += fmt.Sprintf("%s %s.%s", flag, Logic, logic)
		newLogicsParam += fmt.Sprintf("%s %s.%s", flag, Logic, logic)
		newLogicsFields += fmt.Sprintf("%s:%s,", flag, flag)
		if i < len(logics)-1 {
			logicList += "\n\t"
			newLogicsParam += ","
			newLogicsFields += "\n\t\t"
		}
	}

	return
}

func genHandlerImport(isDomain bool, logicList, parentPkg string, api *spec.ApiSpec) string {
	var imports []string
	imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, contextDir)))

	if len(logicList) == 0 {
		return strings.Join(imports, "\n\t")
	}
	imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, pkgHttpxDir)))
	imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, logicDir)))

	if isDomain {
		imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, typesDir)))
	}
	return strings.Join(imports, "\n\t")
}

func genHandlerRouteList(dir, sname string, groupList []spec.Group, filter map[string]struct{}) (routeList string, methodList string, isDomain bool) {
	var (
		handleRouteList strings.Builder
		methods         strings.Builder
	)
	for i, g := range groupList {
		gName := fmt.Sprintf("g%v", i)
		if groupName := g.GetAnnotation("groupName"); len(groupName) > 0 {
			gName = groupName
		} else if len(groupList) == 1 {
			gName = "g"
		}

		handleRouteList.Write([]byte(fmt.Sprintf("%s := engine.Group(\"%s\"", gName, g.GetAnnotation("group"))))
		if str := g.GetAnnotation("middleware"); len(str) > 0 {
			handleRouteList.Write([]byte(fmt.Sprintf(", %s", genHandlerMiddleware(str))))
		}
		handleRouteList.Write([]byte(")"))

		for _, r := range g.Routes {

			// 将 / 转为 ""
			// 如 /v1/user/  转为 /v1/user
			if r.Path == "/" {
				r.Path = ""
			}
			if str := r.GetAnnotation("middleware"); len(str) > 0 {
				handleRouteList.Write([]byte(fmt.Sprintf("\n\t%s.%s(\"%s\", h.%s, %s)", gName, getHttpMethod(r.Method), r.Path, r.Handler, genHandlerMiddleware(str))))
				getRouterMiddleware(dir, str)
			} else {
				handleRouteList.Write([]byte(fmt.Sprintf("\n\t%s.%s(\"%s\", h.%s)", gName, getHttpMethod(r.Method), r.Path, r.Handler)))
			}

			if filter != nil && len(filter) > 0 {
				if _, ok := filter[r.Handler]; ok {
					continue
				}
			}

			methods.Write([]byte(genHandlerMethods(sname, r)))

			if r.RequestType != nil {
				isDomain = true
			}
		}

		if i < len(groupList)-1 {
			handleRouteList.Write([]byte("\n\n\t"))
		}
	}

	return handleRouteList.String(), methods.String(), isDomain
}

func getHttpMethod(m string) string {
	switch m {
	case "get", "GET":
		return "GET"
	case "post", "POST":
		return "POST"
	case "put", "PUT":
		return "PUT"
	case "delete", "DELETE":
		return "DELETE"
	case "options", "OPTIONS":
		return "OPTIONS"
	case "head", "HEAD":
		return "HEAD"
	case "patch", "PATCH":
		return "PATCH"
	case "any", "Any":
		return "Any"
	}

	return "Any"
}

func genHandlerMiddleware(v string) string {
	var res strings.Builder

	middlewares := strings.Split(v, ",")

	for i, middleware := range middlewares {
		res.Write([]byte(fmt.Sprintf("h.svcCtx.%s.Handler", strings.TrimSpace(middleware))))

		if i < len(middlewares)-1 {
			res.Write([]byte(","))
		}
	}

	return res.String()
}

func genHandlerMethods(sname string, r spec.Route) (methods string) {
	var res strings.Builder

	if docs := r.GetAnnotation("doc"); len(docs) > 0 {
		res.Write([]byte(fmt.Sprintf("// %s \n", docs)))
	}

	res.Write([]byte(fmt.Sprintf("func (h *%s) %s(ctx *gin.Context) {\n", sname, r.Handler)))
	if r.RequestType != nil {
		t := r.RequestType.Name()
		res.Write([]byte(fmt.Sprintf("\tvar req %s.%s\n", typesPacket, strings.ToUpper(t[:1])+t[1:])))
		res.Write([]byte("\tif err := httpx.BindAndValidate(ctx, &req); err != nil {\n\t\thttpx.FailWithErr(ctx, err)\n\t\treturn\n\t}\n"))
	}

	defer func() {
		res.Write([]byte("}\n\n"))
		methods = res.String()
	}()

	if len(r.LogicMethod) == 0 {
		if r.RequestType != nil {
			res.Write([]byte("\t_ = req \n"))
		}
		return
	}

	logicObject := strings.ToLower(r.LogicMethod[:1]) + r.LogicMethod[1:]

	if r.RequestType != nil {
		res.Write([]byte("\n\t"))
	}

	if r.ResponseType != nil {
		res.Write([]byte("res, "))
	}
	res.Write([]byte(fmt.Sprintf("err := h.%s(ctx.Request.Context()", logicObject)))
	if r.RequestType != nil {
		res.Write([]byte(", &req"))
	}
	res.Write([]byte(")\n\tif err != nil {\n\t\thttpx.FailWithErr(ctx, err)\n\t} else {\n\t\t"))

	if r.ResponseType != nil {
		res.Write([]byte("httpx.OkWithData(ctx, res)\n\t}\n"))
	} else {
		res.Write([]byte("httpx.Ok(ctx)\n\t}\n"))
	}

	return
}
